package com.acme.javaschool.intense.sems.sem2concurrency.computing;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.acme.javaschool.intense.sems.sem2concurrency.computing.Computable;
import com.acme.javaschool.intense.sems.sem2concurrency.computing.SlowpokeMathematician;
import com.acme.javaschool.intense.sems.sem2concurrency.computing.cached.ConcurrentCachedComputable;
import com.acme.javaschool.intense.sems.sem2concurrency.computing.cached.FutureCachedComputable;
import com.acme.javaschool.intense.sems.sem2concurrency.computing.cached.SynchronizedCachedComputable;
import org.junit.Test;

public class SlowpokeMathComparison {

    public static final int COMPUTE_ARG = 1000000;
    public static final int SPREAD = 100; // COMPUTE_ARG - SPREAD > 0
    private static final int CYCLES = 10000;

    private ExecutorService executor;
    private Computable arbeiter;

    long startNanos = 0;
    long endNanos = 0;

    @Test
    public void test() {
        arbeiter = new SlowpokeMathematician();
        runTests();

        arbeiter = new SynchronizedCachedComputable(new SlowpokeMathematician());
        runTests();

        arbeiter = new ConcurrentCachedComputable(new SlowpokeMathematician());
        runTests();

        arbeiter = new FutureCachedComputable(new SlowpokeMathematician());
        runTests();
    }

    public void runTests() {
        setUp();
        submitTasks(arbeiter);
        tearDown();
        String cl = arbeiter.getClass().toString();
        String c = cl.substring(cl.lastIndexOf('.') + 1);
        System.out.println(c + "\nmade " + arbeiter.getCount()
                + " cycles of " + CYCLES + " (delta=" + (CYCLES - arbeiter.getCount()) + ") in "
                + (endNanos - startNanos) / 1000000 + " millis.\n");
    }

    public void setUp() {
        arbeiter.reset();
        executor = Executors.newFixedThreadPool(32);
        endNanos = 0;
        startNanos = System.nanoTime();
    }

    public void tearDown() {
        executor.shutdown();
        try {
            executor.awaitTermination(20, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            endNanos = System.nanoTime();
        }
        if (endNanos == 0) {
            endNanos = System.nanoTime();
        }
    }

    private void submitTasks(Computable worker) {
        for (int i = 0; i < CYCLES; i++) {
//            executor.submit(newTask(worker, COMPUTE_ARG - i));
            executor.submit(newTask(worker, (int) Math.round((Math.random() - 0.5 ) * SPREAD + COMPUTE_ARG)));
        }
    }

    private Runnable newTask(final Computable worker, final int arg) {
        return new Runnable() {
            @Override
            public void run() {
                try {
                    worker.compute(arg);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
    }

}
